﻿// include Fake lib
#r "packages/FAKE/tools/FakeLib.dll"
open System
open System.IO
open System.Text
open Fake
open Fake.AssemblyInfoFile
open Fake.FileUtils
open Fake.TaskRunnerHelper
open Fake.ProcessHelper

//--------------------------------------------------------------------------------
// Information about the project for Nuget and Assembly info files
//--------------------------------------------------------------------------------

let product = "Dubbo.NET"
let authors = ["Dubbo.NET Team"]
let copyright = "Copyright © 2016-2018 Dubbo.NET Team"
let company = "Dubbo.NET Team"
let description = "Dubbo.NET is a port of the popular Java framework Dubbo to .NET"
let tags = ["Dubbo.NET"; "Rpc"]
let configuration = "Release"
let toolDir = "tools"

//--------------------------------------------------------------------------------
// Read release notes and version
//--------------------------------------------------------------------------------

let parsedRelease = 
    ReadFile "ReleaseNotes.md"
    |> ReleaseNotesHelper.parseReleaseNotes

let envBuildNumber = System.Environment.GetEnvironmentVariable("BUILD_NUMBER")
let buildNumber = if String.IsNullOrWhiteSpace(envBuildNumber) then DateTime.Now.ToString("yyMMddHHmm") else envBuildNumber

let version = parsedRelease.AssemblyVersion + "." + buildNumber
let preReleaseVersion = version + "-beta"

let isUnstableDocs = hasBuildParam "unstable"
let isPreRelease = hasBuildParam "nugetprerelease"
let release = if isPreRelease then ReleaseNotesHelper.ReleaseNotes.New(version, preReleaseVersion, parsedRelease.Notes) else parsedRelease

printfn "Assembly version: %s\nNuget version: %s\n" release.AssemblyVersion release.NugetVersion


//--------------------------------------------------------------------------------
// Directories
//--------------------------------------------------------------------------------

let binDir = "bin"
let nugetDir = binDir @@ "nuget"
let workingDir = binDir @@ "build"
let libDir = workingDir @@ @"lib\net45\"
let nugetExe = FullName @".nuget\NuGet.exe"
let docDir = "bin" @@ "doc"
let sourceBrowserDocsDir = binDir @@ "sourcebrowser"

//--------------------------------------------------------------------------------
// Clean build results
//--------------------------------------------------------------------------------

Target "Clean" (fun _ ->
    CleanDirs [binDir]
)

//--------------------------------------------------------------------------------
// Generate AssemblyInfo files with the version for release notes 

//--------------------------------------------------------------------------------

Target "AssemblyInfo" (fun _ ->
    CreateCSharpAssemblyInfoWithConfig "./SharedAssemblyInfo.cs" [
        Attribute.Company company
        Attribute.Copyright copyright
        Attribute.Product product
        Attribute.Version parsedRelease.AssemblyVersion
        Attribute.FileVersion parsedRelease.AssemblyVersion
        Attribute.ComVisible false ] <| AssemblyInfoFileConfig(false)
)

//--------------------------------------------------------------------------------
// Build the solution
//--------------------------------------------------------------------------------

let buildMode = getBuildParamOrDefault "buildMode" "Release"
let setParams defaults = {
    defaults with
        ToolsVersion = Some("14.0")
        Targets = ["Build"]
        Properties =
            [
                "Configuration", buildMode
            ]
    }

Target "Build" (fun _ ->
  build setParams "./Dubbo.NET.sln"
    |> DoNothing
)

//--------------------------------------------------------------------------------
// Copy the build output to bin directory
//--------------------------------------------------------------------------------

Target "CopyOutput" <| fun _ ->
    let copyOutput project =
        let src = project @@ @"bin/Release/"
        let dst = binDir @@ project
        CopyDir dst src allFiles
    [
        "Dubbo"
        "Dubbo.Common"
        "Dubbo.Config.Api"
        "Dubbo.Config.Spring"
        "Dubbo.Container.Host"
        "Dubbo.Container.Log4Net"
        "Dubbo.Container.Spring"
        "Dubbo.Rpc"
        "Dubbo.Rpc.Thrift"
      ]
    |> List.iter copyOutput

Target "BuildRelease" DoNothing

//--------------------------------------------------------------------------------
// Nuget targets 
//--------------------------------------------------------------------------------

module Nuget = 
    // add Dubbo dependency for other projects
    let getMyDependency project =
        match project with
        | "Dubbo" -> ["Dubbo.Config.Spring", release.NugetVersion; "Dubbo.Rpc.Thrift", preReleaseVersion]
        | "Dubbo.Common" -> []
        | "Dubbo.Config.Api" -> ["Dubbo.Common", release.NugetVersion]
        | "Dubbo.Config.Spring" -> ["Dubbo.Config.Api", release.NugetVersion;]
        | rpc when (rpc.StartsWith("Dubbo.Rpc.")) -> ["Dubbo.Rpc", release.NugetVersion]
        | "Dubbo.Rpc" -> ["Dubbo.Common", release.NugetVersion]
        | "Dubbo.Container.Host"  -> ["Dubbo.Container.Log4Net", release.NugetVersion; "Dubbo.Container.Spring", release.NugetVersion]
        | "Dubbo.Container.Spring"  -> ["Dubbo.Common", release.NugetVersion; "Dubbo.Config.Spring", release.NugetVersion]
        | container when (container.StartsWith("Dubbo.Container.")) -> ["Dubbo.Common", release.NugetVersion]
        | _ -> []

    // used to add -pre suffix to pre-release packages
    let getProjectVersion project =
      match project with
      | "Dubbo" -> preReleaseVersion
      | "Dubbo.Rpc.Thrift" -> preReleaseVersion
      | _ -> release.NugetVersion

open Nuget

//--------------------------------------------------------------------------------
// Clean nuget directory
//--------------------------------------------------------------------------------

Target "CleanNuget" <| fun _ ->
    CleanDir nugetDir

//--------------------------------------------------------------------------------
// Pack nuget for all projects
// Publish to nuget.org if nugetkey is specified
//--------------------------------------------------------------------------------

let createNugetPackages _ =
    let removeDir dir = 
        let del _ = 
            DeleteDir dir
            not (directoryExists dir)
        runWithRetries del 3 |> ignore

    let getReleaseFiles project releaseDir =
        match project with
        | "Dubbo" ->
            !! (releaseDir @@ "*.dll")
            -- (releaseDir @@ "*.dll")
        | _ ->
            !! (releaseDir @@ project + ".dll")
            ++ (releaseDir @@ project + ".pdb")
            ++ (releaseDir @@ project + ".xml")

    let getExternalPackages project packagesFile =
        match project with
        | "Akka.MultiNodeTestRunner" -> [] // because the MNTR is an exe, all of its dlls have to be available in the same working directory when it executes
        | _ -> if (fileExists packagesFile) then (getDependencies packagesFile) else []

    ensureDirectory nugetDir
    for nuspec in !! "**/*.nuspec" do
        printfn "Creating nuget packages for %s" nuspec
        
        CleanDir workingDir

        let project = Path.GetFileNameWithoutExtension nuspec 
        let projectDir = Path.GetDirectoryName nuspec
        let projectFile = (!! (projectDir @@ project + ".*sproj")) |> Seq.head
        let releaseDir = projectDir @@ @"bin\Release"
        let packages = projectDir @@ "packages.config"
        let packageDependencies = getExternalPackages project packages
        let dependencies = packageDependencies @ getMyDependency project
        let releaseVersion = getProjectVersion project

        let pack outputDir symbolPackage =
            NuGetHelper.NuGet
                (fun p ->
                    { p with
                        Description = description
                        Authors = authors
                        Copyright = copyright
                        Project =  project
                        Properties = ["Configuration", "Release"]
                        ReleaseNotes = release.Notes |> String.concat "\n"
                        Version = releaseVersion
                        Tags = tags |> String.concat " "
                        OutputPath = outputDir
                        WorkingDir = workingDir
                        SymbolPackage = symbolPackage
                        Dependencies = dependencies })
                nuspec

        // Copy dll, pdb and xml to libdir = workingDir/lib/net45/
        ensureDirectory libDir
//        getReleaseFiles project releaseDir
//        |> CopyFiles libDir

        let libFiles = [project + ".exe"; project + ".dll"; project + ".pdb"; project + ".xml";]
        let isLibFile f = 
            let fileName = filename f
            match project with
            | "Dubbo" -> false
            | _ -> Seq.exists ((=) fileName) libFiles

        CopyDir libDir releaseDir isLibFile

        // Copy all src-files (.cs and .fs files) to workingDir/src
        let nugetSrcDir = workingDir @@ @"src/"
        // CreateDir nugetSrcDir

        let isCs = hasExt ".cs"
        let isAssemblyInfo f = (filename f).Contains("AssemblyInfo")
        let isSrc f = (isCs f) && not (isAssemblyInfo f) 
        CopyDir nugetSrcDir projectDir isSrc
        
        //Remove workingDir/src/obj and workingDir/src/bin
        removeDir (nugetSrcDir @@ "obj")
        removeDir (nugetSrcDir @@ "bin")

        let projNugetDir = projectDir @@ @"NuGet/";
        if (directoryExists projNugetDir) then
            CopyDir workingDir projNugetDir allFiles

        if (project.Equals("Dubbo.Container.Host")) then
            // Copy all content-files (.config, .transform and .xdt files) to workingDir/content
            let contentDir = workingDir @@ @"content/";

            let isConf f = (filename f).Equals(project + ".exe.config")
            let isTransf = hasExt ".transform"
            let isXdt = hasExt ".xdt"
            let isContent f = (isConf f) || (isTransf f) || (isXdt f)
            CopyDir contentDir projectDir isContent

            removeDir (contentDir @@ "obj")
            removeDir (contentDir @@ "bin")

        // Create both normal nuget package and symbols nuget package. 
        // Uses the files we copied to workingDir and outputs to nugetdir
        pack nugetDir NugetSymbolPackage.Nuspec
        
        removeDir workingDir

let publishNugetPackages _ = 
    let rec publishPackage url accessKey trialsLeft packageFile =
        let tracing = enableProcessTracing
        enableProcessTracing <- false
        let args p =
            match p with
            | (pack, key, "") -> sprintf "push \"%s\" %s" pack key
            | (pack, key, url) -> sprintf "push \"%s\" %s -source %s" pack key url

        tracefn "Pushing %s Attempts left: %d" (FullName packageFile) trialsLeft
        try 
            let result = ExecProcess (fun info -> 
                    info.FileName <- nugetExe
                    info.WorkingDirectory <- (Path.GetDirectoryName (FullName packageFile))
                    info.Arguments <- args (packageFile, accessKey,url)) (System.TimeSpan.FromMinutes 1.0)
            enableProcessTracing <- tracing
            if result <> 0 then failwithf "Error during NuGet symbol push. %s %s" nugetExe (args (packageFile, "key omitted",url))
        with exn -> 
            if (trialsLeft > 0) then (publishPackage url accessKey (trialsLeft-1) packageFile)
            else raise exn
    let shouldPushNugetPackages = hasBuildParam "nugetkey"
    let shouldPushSymbolsPackages = (hasBuildParam "symbolspublishurl") && (hasBuildParam "symbolskey")

    if (shouldPushNugetPackages || shouldPushSymbolsPackages) then
        printfn "Pushing nuget packages"
        if shouldPushNugetPackages then
            let normalPackages= 
                !! (nugetDir @@ "*.nupkg") 
                -- (nugetDir @@ "*.symbols.nupkg") |> Seq.sortBy(fun x -> x.ToLower())
            for package in normalPackages do
                try
                    publishPackage (getBuildParamOrDefault "nugetpublishurl" "") (getBuildParam "nugetkey") 3 package
                with exn ->
                    printfn "%s" exn.Message

        if shouldPushSymbolsPackages then
            let symbolPackages= !! (nugetDir @@ "*.symbols.nupkg") |> Seq.sortBy(fun x -> x.ToLower())
            for package in symbolPackages do
                try
                    publishPackage (getBuildParam "symbolspublishurl") (getBuildParam "symbolskey") 3 package
                with exn ->
                    printfn "%s" exn.Message


Target "Nuget" <| fun _ -> 
    createNugetPackages()
    publishNugetPackages()

Target "CreateNuget" <| fun _ -> 
    createNugetPackages()

Target "PublishNuget" <| fun _ ->
    publishNugetPackages()

//--------------------------------------------------------------------------------
// Help 
//--------------------------------------------------------------------------------

Target "Help" <| fun _ ->
    List.iter printfn [
      "usage:"
      "build [target]"
      ""
      " Targets for building:"
      " * Build      Builds"
      " * Nuget      Create and optionally publish nugets packages"
      " * All        Builds, run tests, creates and optionally publish nuget packages"
      ""
      " Other Targets"
      " * Help       Display this help" 
      " * HelpNuget  Display help about creating and pushing nuget packages" 
      ""]

Target "HelpNuget" <| fun _ ->
    List.iter printfn [
      "usage: "
      "build Nuget [nugetkey=<key> [nugetpublishurl=<url>]] "
      "            [symbolskey=<key> symbolspublishurl=<url>] "
      "            [nugetprerelease=<prefix>]"
      ""
      "Arguments for Nuget target:"
      "   nugetprerelease=<prefix>   Creates a pre-release package."
      "                              The version will be version.<date>-prefix"
      "                              Example: nugetprerelease=beta =>"
      "                                       0.6.3.1408191917-beta"
      ""
      "In order to publish a nuget package, keys must be specified."
      "If a key is not specified the nuget packages will only be created on disk"
      "After a build you can find them in bin/nuget"
      ""
      "For pushing nuget packages to nuget.org and symbols to symbolsource.org"
      "you need to specify nugetkey=<key>"
      "   build Nuget nugetKey=<key for nuget.org>"
      ""
      "For pushing the ordinary nuget packages to another place than nuget.org specify the url"
      "  nugetkey=<key>  nugetpublishurl=<url>  "
      ""
      "For pushing symbols packages specify:"
      "  symbolskey=<key>  symbolspublishurl=<url> "
      ""
      "Examples:"
      "  build Nuget                      Build nuget packages to the bin/nuget folder"
      ""
      "  build Nuget nugetprerelease      Build pre-release nuget packages"
      ""
      "  build Nuget nugetkey=123         Build and publish to nuget.org and symbolsource.org"
      ""
      "  build Nuget nugetprerelease nugetkey=123 nugetpublishurl=http://abc"
      "              symbolskey=456 symbolspublishurl=http://xyz"
      "                                   Build and publish pre-release nuget packages to http://abc"
      "                                   and symbols packages to http://xyz"
      ""]

//--------------------------------------------------------------------------------
//  Target dependencies
//--------------------------------------------------------------------------------

// build dependencies
"Clean" ==> "AssemblyInfo" ==> "Build" ==> "CopyOutput" ==> "BuildRelease"

// nuget dependencies
"CleanNuget" ==> "CreateNuget"
"CleanNuget" ==> "BuildRelease" ==> "Nuget"

Target "All" DoNothing
"BuildRelease" ==> "All"
"Nuget" ==> "All"

// start build
RunTargetOrDefault "Help"